package com.semanticweb.project.api;

import java.util.ArrayList;
import java.util.List;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.query.Dataset;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFactory;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.reasoner.IllegalParameterException;
import com.hp.hpl.jena.sdb.SDBFactory;
import com.hp.hpl.jena.sdb.Store;
import com.hp.hpl.jena.sdb.StoreDesc;
import com.hp.hpl.jena.sdb.sql.JDBC;
import com.hp.hpl.jena.sdb.sql.SDBConnection;
import com.hp.hpl.jena.sdb.store.DatabaseType;
import com.hp.hpl.jena.sdb.store.DatasetStore;
import com.hp.hpl.jena.sdb.store.LayoutType;
import com.hp.hpl.jena.util.FileManager;
import com.semanticweb.project.util.ErrorString;
import com.semanticweb.project.util.StringUtil;

public class OntologyImpl implements Ontology {

	private final Store store;
	private final OntModel model;
	private final Model dbModel;

	public OntologyImpl(final String dbUrl, final String name, final String pw, final DatabaseType dbType, final String ontologyModelFilePath,
			final String ontologyNamespace, final String dbDriver) {
		loadDatabaseDriver(dbDriver);

		final SDBConnection connection = connectToDatabase(dbUrl, name, pw);
		this.store = connectToStore(connection, dbType);

		final boolean dbExists = dbExists();
		if (!dbExists) {
			createDB();
		}

		this.dbModel = SDBFactory.connectDefaultModel(this.store);

		this.model = ModelFactory.createOntologyModel();

		if (!dbExists) {
			this.model.add(loadOntologyFromFile(ontologyModelFilePath, ontologyNamespace));
		} else {
			this.model.add(this.dbModel);
		}
	}

	@Override
	public void loadDatabaseDriver(final String dbDriverClass) {
		if (StringUtil.isNullOrEmpty(dbDriverClass)) {
			throw new IllegalArgumentException(ErrorString.parameterEmptyOrNull("dbDriverClass"));
		}

		JDBC.loadDriver(dbDriverClass);
	}

	@Override
	public SDBConnection connectToDatabase(final String url, final String name, final String pw) {
		if (StringUtil.isNullOrEmpty(url)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("url"));
		}

		return new SDBConnection(url, name, pw);
	}

	@Override
	public Store connectToStore(final SDBConnection connection, final DatabaseType dbType) {
		if (connection == null) {
			throw new IllegalParameterException(ErrorString.parameterNull("connection"));
		}
		if (dbType == null) {
			throw new IllegalParameterException(ErrorString.parameterNull("dbType"));
		}

		final StoreDesc storeDesc = new StoreDesc(LayoutType.LayoutTripleNodesHash, dbType);
		return SDBFactory.connectStore(connection, storeDesc);
	}

	@Override
	public OntModel loadOntologyFromFile(final String filePath, final String namespace) {
		if (StringUtil.isNullOrEmpty(filePath)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("filePath"));
		}
		if (StringUtil.isNullOrEmpty(namespace)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("namespace"));
		}

		final OntModel tempModel = ModelFactory.createOntologyModel();

		tempModel.read(FileManager.get().open(filePath), namespace);
		return tempModel;
	}

	@Override
	public void saveModel() {
		this.dbModel.add(this.model);
	}

	@Override
	public void closeDBConnection() {
		this.store.close();
		this.store.getConnection().close();
	}

	@Override
	public void createDB() {
		this.store.getTableFormatter().create();
	}

	@Override
	public void destroyDB() {
		this.store.getTableFormatter().truncate();
	}

	@Override
	public boolean dbExists() {
		try {
			return this.store.getSize() > 0;
		} catch (final Exception e) {
			return false;
		}
	}

	@Override
	public Store getStore() {
		return this.store;
	}

	@Override
	public OntModel getModel() {
		return this.model;
	}

	@Override
	public void createIndividual(final String individualClass, final String individualName) {
		if (StringUtil.isNullOrEmpty(individualClass)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("individualClass"));
		}
		if (StringUtil.isNullOrEmpty(individualName)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("individualName"));
		}

		if (this.model.getIndividual(individualName) == null) {
			this.model.getOntClass(individualClass).createIndividual(individualName);
		}
	}

	@Override
	public void connectIndividuals(final String subject, final String predicate, final String object) {
		if (StringUtil.isNullOrEmpty(subject)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("subject"));
		}
		if (StringUtil.isNullOrEmpty(predicate)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("predicate"));
		}
		if (StringUtil.isNullOrEmpty(object)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("object"));
		}

		final Individual individual = this.model.getIndividual(subject);
		if (individual != null) {
			this.model.add(this.model.createStatement(individual, this.model.getProperty(predicate), this.model.getIndividual(object)));
		}
	}

	@Override
	public void addLabel(final String individualName, final String label) {
		if (StringUtil.isNullOrEmpty(individualName)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("individualName"));
		}
		if (StringUtil.isNullOrEmpty(label)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("label"));
		}

		final Individual individual = this.model.getIndividual(individualName);
		if (individual != null) {
			individual.addLabel(label, null);
		}
	}

	@Override
	public ResultSet query(final String query) {
		if (StringUtil.isNullOrEmpty(query)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("query"));
		}

		final Query q = QueryFactory.create(query);
		final Dataset ds = DatasetStore.create(this.store);
		ResultSet retVal = null;

		final QueryExecution qe = QueryExecutionFactory.create(q, ds);
		try {
			final ResultSet rs = qe.execSelect();
			retVal = ResultSetFactory.copyResults(rs);
		} finally {
			qe.close();
		}

		return retVal;
	}

	@Override
	public String[] queryStr(final String query, final String item) {
		if (StringUtil.isNullOrEmpty(query)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("query"));
		}
		if (StringUtil.isNullOrEmpty(item)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("item"));
		}

		final List<String> result = new ArrayList<String>();
		final ResultSet rs = query(query);
		while (rs.hasNext()) {
			final QuerySolution s = rs.nextSolution();
			final RDFNode node = s.get(item);
			result.add(node.toString());
		}

		return result.toArray(new String[0]);
	}

	public boolean askQuery(final String query) {
		if (StringUtil.isNullOrEmpty(query)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("query"));
		}

		final Query q = QueryFactory.create(query);
		final Dataset ds = DatasetStore.create(this.store);
		final QueryExecution qe = QueryExecutionFactory.create(q, ds);

		return qe.execAsk();
	}

	@Override
	public String getLabel(final String resource) {
		if (StringUtil.isNullOrEmpty(resource)) {
			throw new IllegalParameterException(ErrorString.parameterEmptyOrNull("resource"));
		}

		final Individual individual = this.model.getIndividual(resource);
		if (individual == null) {
			return null;
		} else {
			return individual.getLabel(null);
		}
	}
}
